// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

// const TROJAN_REMOTE_ADDRESS: &str = "7890";
// const PRIVOXY_HTTP_LISTEN: &str = "56784";
// const PRIVOXY_SOCKS_LISTEN: &str = "7890";

pub mod win;
use std::{
    collections::HashMap,
    fs::{File, OpenOptions},
    io::{Read, Write, Error},
    path::Path,
    sync::{Arc, Mutex},
    time::Duration,
    env,
    fs
};
use regex::Regex;
use backtrace::Backtrace;
use log::LevelFilter;
use serde::{Deserialize, Serialize};
use tauri::{
    api::process::{Command, CommandChild, CommandEvent},
    CustomMenuItem, Icon, Manager, RunEvent, State, SystemTray, SystemTrayEvent, SystemTrayMenu,
    Window, WindowEvent, Wry,
};
use tauri_plugin_log::LogTarget;
use crypto::md5::Md5;
use crypto::digest::Digest;

#[derive(Deserialize, Serialize, Debug, Default, Clone)]
pub struct Config {
    pub addres: String,
    pub port: u32,
    pub pwd: String
}

#[derive(Debug, Clone)]
enum ConnectionState {
    Connecting,
    Connected,
    Disconnecting,
    Disconnected,
}

pub struct ClashProxy {
    config: Config,
    clash: Option<CommandChild>,
    running_icon: Icon,
    stopped_icon: Icon,
    state: ConnectionState
}

impl ClashProxy {
    fn new() -> ClashProxy {
        ClashProxy {
            config: init_config().unwrap_or_default(),
            clash:  None,
            running_icon: Icon::Raw(include_bytes!("../icons/icon.ico").to_vec()),
            stopped_icon: Icon::Raw(include_bytes!("../icons/icon.ico").to_vec()),
            state: ConnectionState::Disconnected
        }
    }
}

type ClashState = Arc<Mutex<ClashProxy>>;

#[tauri::command]
fn set_config(config: Config, state: State<ClashState>) {
    state.lock().unwrap().config = config;
    let _config = state.lock().unwrap().config.clone();

    let output = Command::new("REG")
        .args(&["QUERY", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", "/v", "ProxyEnable"])
        .output()
        .expect("Failed to execute command");
    let stdout = output.stdout.to_string();
    let lines: Vec<&str> = stdout.split('\n').collect();

    let proxy_enable_value: u32;
    if let Some(value_str) = lines.iter().find(|line| line.contains("ProxyEnable")) {
        if let Some(proxy_enable_str) = value_str.split_whitespace().last() {
            proxy_enable_value = match proxy_enable_str {
                "0x1" => 1,
                "0x0" => 0,
                _ => proxy_enable_str.parse::<u32>().unwrap_or(0)
            };
        } else {
            proxy_enable_value = 0;
        }
    } else {
        proxy_enable_value = 0;
    }
        
        // Enable or disable the proxy based on its current state
        // if proxy_enable_value == 0 {
        //     println!("系统代理目前处于关闭状态，正在开启代理并关闭tun模式，请稍候...");
            
        //     Command::new("reg")
        //         .args(&["add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", "/v", "ProxyEnable", "/t", "REG_DWORD", "/d", "1", "/f"])
        //         .output()
        //         .expect("Failed to execute command");
            
        //     Command::new("reg")
        //         .args(&["add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", "/v", "ProxyServer", "/d", "127.0.0.1:56784", "/f"])
        //         .output()
        //         .expect("Failed to execute command");
        
        //     println!("系统代理已开启，tun模式已关闭");
        // } else 
    if proxy_enable_value == 1 {
        log::info!("系统代理目前处于开启状态，正在关闭代理并开启tun模式，请稍候...");
        
        Command::new("reg")
            .args(&["add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", "/v", "ProxyEnable", "/t", "REG_DWORD", "/d", "0", "/f"])
            .output()
            .expect("Failed to execute command");
        
        Command::new("reg")
            .args(&["add", "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", "/v", "ProxyServer", "/d", "", "/f"])
            .output()
            .expect("Failed to execute command");

        log::info!("系统代理已关闭，tun模式已开启");
    }

    let mut hasher = Md5::new();
    let text = format!("{}{}", _config.addres, _config.pwd);
    hasher.input_str(&text);

    //配置文件操作开始
    copy_file("clash/Country.mmdb", "Country.mmdb", state.clone());
    copy_file("clash/wintun.dll", "wintun.dll", state.clone());

    let config = read_file("clash/config.yaml");

    let re_address = Regex::new(r"\{REMOTE_ADDRESS\}").unwrap();
    let re_port = Regex::new(r"\{REMOTE_PORT\}").unwrap();
    let re_password = Regex::new(r"\{PASSWORD\}").unwrap();

    let result = re_address.replace_all(&config, _config.addres);
    let result = re_port.replace_all(&result, _config.port.to_string());
    let result = re_password.replace_all(&result, _config.pwd);

    if let Err(err) = save_config("config.yaml", result.into_owned(), state.clone()) {
        log::error!("save config Failure:{:?}", err);
        return;
    }
    
    log::info!("save clash config Success");
}

#[tauri::command]
fn start_vpn(state: State<ClashState>, window: Window<Wry>) {
    log::info!("start clash now");

    let locked_state: ConnectionState = state.lock().unwrap().state.clone();
    match locked_state {
        ConnectionState::Connecting | ConnectionState::Connected => {
            emit_state_update_event(locked_state, window.clone());
            return
        },
        _ => {},
    };

    //准备路径
    let file_path = get_file_path("", state.clone());
    
    state.lock().unwrap().state = ConnectionState::Connecting;
    emit_state_update_event(ConnectionState::Connecting, window.clone());

    let _config = state.lock().unwrap().config.clone();
    let state = state.inner().clone();
    tauri::async_runtime::spawn(async move {
        let mut rxs = HashMap::new();
        
        let args3 = vec![
            "-d",
            file_path.to_str().unwrap()
        ];
        
        log::info!("{:?}", args3);
        match Command::new_sidecar("clash").unwrap().args(args3).spawn() {
            Ok((rx, child)) => {
                state.lock().unwrap().clash.replace(child);
                rxs.insert("clash", rx);
                log::info!("run clash Success");
                start_proxy();
            }
            Err(err) => {
                log::error!("start clash Failure:{:?}", err);
            }
        }
        
        state.lock().unwrap().state = ConnectionState::Connected;
        emit_state_update_event(ConnectionState::Connected, window.clone());
        log::info!("sub process started");

        while !rxs.is_empty() {
            let exited: Vec<_> = rxs
                .iter_mut()
                .filter_map(|(name, rx)| {
                    let exit = match rx.try_recv() {
                        Ok(CommandEvent::Terminated(payload)) => {
                            log::info!("{} exits with:{:?}", name, payload);
                            true
                        }
                        Ok(CommandEvent::Error(err)) => {
                            log::info!("{} got error:{}", name, err);
                            false
                        }
                        Ok(CommandEvent::Stderr(err)) => {
                            log::info!("{} got stderr:{}", name, err);
                            false
                        }
                        Ok(CommandEvent::Stdout(output)) => {
                            log::info!("{} got stdout:{}", name, output);
                            false
                        }
                        Err(_err) => false,
                        Ok(_) => false,
                    };
                    if exit {
                        let mut state = state.lock().unwrap();
                        match *name {
                            "clash" => {
                                state.clash.take();
                                // if let Some(child) = state.trojan.take() {
                                //     let _ = child.kill();
                                // }
                            }
                            _ => {
                                log::error!("invalid name:{}", name);
                            }
                        }
                        Some(name.to_string())
                    } else {
                        None
                    }
                })
                .collect();
            for name in exited {
                rxs.remove(name.as_str());
            }
            tokio::time::sleep(Duration::from_millis(66)).await;
        }
        state.lock().unwrap().state = ConnectionState::Disconnected;
        emit_state_update_event(ConnectionState::Disconnected, window);
        log::info!("sub process exits");
    });
}


#[tauri::command]
fn stop_vpn(state: State<ClashState>, window: Window<Wry>) {
    log::info!("stop clash now");

    let locked_state: ConnectionState = state.lock().unwrap().state.clone();
    match locked_state {
        ConnectionState::Disconnecting | ConnectionState::Disconnected => {
            emit_state_update_event(locked_state, window);
            return
        },
        _ => {},
    };

    state.lock().unwrap().state = ConnectionState::Disconnecting;
    emit_state_update_event(ConnectionState::Disconnecting, window.clone());

    let root_path = get_file_path("", state.clone());
    
    let state = state.inner().clone();
    tauri::async_runtime::spawn(async move {
        let mut page_state = state.lock().unwrap();

        if let Some(child) = page_state.clash.take() {
            let _ = child.kill();
            log::info!("clash stopped Success");
    
            if root_path.exists(){
                let _ = fs::remove_dir(root_path.to_str().unwrap());
            }
        }else {
            page_state.state = ConnectionState::Disconnected;
            log::error!("clash stopped Failure");
            if root_path.exists(){
                let _ = fs::remove_dir(root_path.to_str().unwrap());
            }
            //停止错误触发事件，正常停止会在上面接口触发本事件
            emit_state_update_event(ConnectionState::Disconnected, window.clone());
        }
        
        stop_proxy();
    });
}


fn emit_state_update_event(state: ConnectionState, window: Window<Wry>) {
    window.emit("state-update", format!("{:?}", state.clone())).unwrap();
    let running = match state {
        ConnectionState::Connecting | ConnectionState::Connected => true,
        _ => false,
    };

    let app = window.app_handle();
    let state = app.state::<ClashState>();
    let state = state.lock().unwrap();
    
    let icon = if running {
        state.running_icon.clone()
    } else {
        state.stopped_icon.clone()
    };
    window.set_icon(icon.clone()).unwrap();
    window.app_handle().tray_handle().set_icon(icon).unwrap();
}

fn save_config(name: &str,config: String, state: State<ClashState>) -> Result<(), Error> {
    let _config = state.lock().unwrap().config.clone();

    let mut hasher = Md5::new();
    let text = format!("{}{}", _config.addres, _config.pwd);
    hasher.input_str(&text);

    let mut root_path = env::temp_dir();
    root_path.push(hasher.result_str());
    if !root_path.exists() {
        fs::create_dir(root_path).unwrap();
    }
    
    let mut file_path = env::temp_dir();
    file_path.push(hasher.result_str());
    file_path.push(name);
    log::info!("{:?}", file_path);

    let mut file = OpenOptions::new()
        .create(true)
        .truncate(true)
        .write(true)
        .open(file_path)?;
    file.write_all(config.as_bytes())?;

    Ok(())
}

fn copy_file(source: &str, name:&str, state: State<ClashState>) -> std::path::PathBuf{
    let _config = state.lock().unwrap().config.clone();

    let mut hasher = Md5::new();
    let text = format!("{}{}", _config.addres, _config.pwd);
    hasher.input_str(&text);

    let mut root_path = env::temp_dir();
    root_path.push(hasher.result_str());
    if !root_path.exists() {
        fs::create_dir(root_path).unwrap();
    }

    let mut file_path = env::temp_dir();
    file_path.push(hasher.result_str());
    file_path.push(name);

    if !file_path.exists() {
        fs::copy(Path::new(source), file_path.clone()).expect("无法复制文件");
    }
    file_path
}

fn get_file_path(name: &str, state: State<ClashState>)-> std::path::PathBuf{
    let _config = state.lock().unwrap().config.clone();

    let mut hasher = Md5::new();
    let text = format!("{}{}", _config.addres, _config.pwd);
    hasher.input_str(&text);

    let mut file_path = env::temp_dir();
    file_path.push(hasher.result_str());
    if name!="" {
        file_path.push(name);
    }
    file_path
}

fn read_file(name: &str) -> String {
    let mut file = File::open(name).expect("Failed to open file");
    let mut content = String::new();
    file.read_to_string(&mut content).expect("Failed to read file");
    content
}

fn start_proxy(){
    // let server_addr = format!("127.0.0.1:{}", PRIVOXY_HTTP_LISTEN);
    // let _ = win::homo_proxy(server_addr, true);
}

fn stop_proxy(){
    // let _ = win::homo_proxy("", false);
}

fn init_config() -> Result<Config, Error> {
    Ok(Config {
        port: 443,
        ..Config::default()
    })
}

fn main() {
    let path = Path::new("logs");
    if !path.exists() {
        std::fs::create_dir(path).unwrap();
    }
    std::panic::set_hook(Box::new(|info| {
        let trace = Backtrace::new();
        let message = info.to_string();
        if let Ok(mut file) = OpenOptions::new()
            .write(true)
            .append(true)
            .create(true)
            .open("logs\\crash.log")
        {
            let _ = write!(
                &mut file,
                "[{}]client crash with error:{}\ntrace:{:?}\n",
                chrono::Local::now().format("[%Y-%m-%d %H:%M:%S%.6f]"),
                message,
                trace
            );
        }
    }));

    let _quit = CustomMenuItem::new("quit".to_string(), "退出");
    let _dev = CustomMenuItem::new("dev".to_string(), "开发工具");
    let menu = SystemTrayMenu::new();

    #[cfg(debug_assertions)]
    let menu = menu
        .add_item(_dev)
        .add_native_item(tauri::SystemTrayMenuItem::Separator);
    let menu = menu.add_item(_quit);
    let tray = SystemTray::new().with_menu(menu);

    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![set_config, start_vpn, stop_vpn])
        .system_tray(tray)
        .plugin(
            tauri_plugin_log::Builder::default()
                .targets([LogTarget::LogDir, LogTarget::Webview, LogTarget::Stdout])
                .format(|callback, args, record| {
                    callback.finish(format_args!(
                        "{}[{}:{}][{}]{}",
                        chrono::Local::now().format("[%Y-%m-%d %H:%M:%S%.6f]"),
                        record.file().unwrap_or("tauri"),
                        record.line().unwrap_or(0),
                        record.level(),
                        args
                    ))
                })
                .level(LevelFilter::Info)
                .build(),
        )
        .plugin(tauri_plugin_single_instance::init(|app, args, cwd| {
            log::info!(
                "app:{}, args:{:?}, cwd:{}",
                app.package_info().name,
                args,
                cwd
            );
        }))
        .manage(Arc::new(Mutex::new(ClashProxy::new())))
        .on_system_tray_event(|app, event| match event {
            SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
                "quit" => {
                    let state: State<ClashState> = app.state();
                    let mut state = state.lock().unwrap();
                    if let Some(leaf) = state.clash.take() {
                        let _ = leaf.kill();
                    }
                    std::process::exit(0);
                }
                #[cfg(debug_assertions)]
                "dev" => {
                    let window = app.get_window("main").unwrap();
                    if !window.is_devtools_open() {
                        window.open_devtools();
                    }
                }
                _ => {}
            },
            SystemTrayEvent::DoubleClick { .. } => {
                let window = app.get_window("main").unwrap();
                window.show().unwrap();
                window.set_focus().unwrap();
            }
            _ => {}
        })
        .setup(|app| {
            let state: State<ClashState> = app.state();
            state.lock().unwrap().state = ConnectionState::Disconnected;
            emit_state_update_event(ConnectionState::Disconnected, app.get_window("main").unwrap());
            Ok(())
        })
        .build(tauri::generate_context!())
        .expect("error while running tauri application")
        .run(|app, event| match event {
            RunEvent::WindowEvent {
                event: WindowEvent::CloseRequested { api, .. },
                ..
            } => {
                app.get_window("main").unwrap().hide().unwrap();
                api.prevent_close();
            }
            _ => {}
        });
}
